/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       gpuprogram.cpp
 * Author:     karooolek
 * Created on: 2009-03-19
 *
 **********************************************************************************************************************/

#include "gpuprogram.h"

namespace mGameEngine
{
namespace Graphics
{

GPUProgram::GPUProgram() :
    _program(0), _vertexShader(0), _fragmentShader(0),
    _vertexSource(), _fragmentSource(), _compiled(false)
{
    // no shaders support
    if(!GLEE_VERSION_2_0)
    {
        return;
    }

    _program = glCreateProgram();

    _vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glAttachShader(_program, _vertexShader);

    _fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glAttachShader(_program, _fragmentShader);
}

GPUProgram::GPUProgram(const String &vertexSource, const String &fragmentSource, bool compile) :
    _program(0), _vertexShader(0), _fragmentShader(0),
    _vertexSource(vertexSource), _fragmentSource(fragmentSource),
    _compiled(false)
{
    // no shaders support
    if(!GLEE_VERSION_2_0)
    {
        return;
    }

    _program = glCreateProgram();

    _vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glAttachShader(_program, _vertexShader);

    _fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glAttachShader(_program, _fragmentShader);

    // compile if needed
    if(compile)
    {
        GPUProgram::compile();
    }
}

GPUProgram::~GPUProgram()
{
    // no shaders support
    if(!GLEE_VERSION_2_0)
    {
        return;
    }

    glDeleteProgram(_program);
    glDeleteShader(_vertexShader);
    glDeleteShader(_fragmentShader);
}

void GPUProgram::compile()
{
    // no shaders support or program already compiled
    if(!GLEE_VERSION_2_0 || _compiled)
    {
        return;
    }

    // no vertex or fragment shader source
    if(!_vertexShader || !_fragmentShader)
    {
        return;
    }

    // compilation log
    char logBuffer[1024];
    int logLen;

    // compile vertex shader
    const char *src;
    int status;

    src = _vertexSource;
    glShaderSource(_vertexShader, 1, &src, NULL);
    glCompileShader(_vertexShader);

    glGetShaderiv(_vertexShader, GL_INFO_LOG_LENGTH, &logLen);
    glGetShaderInfoLog(_vertexShader, sizeof(logBuffer), &logLen, logBuffer);
    _compilationLog += "[Compiling vertex shader]\n";
    _compilationLog += logBuffer;

    glGetShaderiv(_vertexShader, GL_COMPILE_STATUS, &status);
    if(!status)
    {
        return;
    }

    // compile vertex shader
    src = _fragmentSource;
    glShaderSource(_fragmentShader, 1, &src, NULL);
    glCompileShader(_fragmentShader);

    glGetShaderiv(_fragmentShader, GL_INFO_LOG_LENGTH, &logLen);
    glGetShaderInfoLog(_fragmentShader, sizeof(logBuffer), &logLen, logBuffer);
    _compilationLog += "[Compiling fragment shader]\n";
    _compilationLog += logBuffer;

    glGetShaderiv(_fragmentShader, GL_COMPILE_STATUS, &status);
    if(!status)
    {
        return;
    }

    // link program
    glLinkProgram(_program);

    glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logLen);
    glGetProgramInfoLog(_program, sizeof(logBuffer), &logLen, logBuffer);
    _compilationLog += "[Linking]\n";
    _compilationLog += logBuffer;

    glGetProgramiv(_program, GL_LINK_STATUS, &status);
    if(!status)
    {
        return;
    }

    // compilation successful
    _compiled = true;
}

void GPUProgram::setValue(const String &name, int a)
{
    use();
    glUniform1i(glGetUniformLocation(_program, name.c_str()), a);
}
void GPUProgram::setValue(const String &name, float a)
{
    use();
    glUniform1f(glGetUniformLocation(_program, name.c_str()), a);
}

void GPUProgram::setVector(const String &name, const Vector2 &v)
{
    use();
    glUniform2fv(glGetUniformLocation(_program, name.c_str()), 1, v);
}
void GPUProgram::setVector(const String &name, const Vector3 &v)
{
    use();
    glUniform3fv(glGetUniformLocation(_program, name.c_str()), 1, v);
}
void GPUProgram::setVector(const String &name, const Vector4 &v)
{
    use();
    glUniform4fv(glGetUniformLocation(_program, name.c_str()), 1, v);
}

void GPUProgram::setMatrix(const String &name, const Matrix2x2 &m)
{
    use();
    glUniformMatrix2fv(glGetUniformLocation(_program, name.c_str()), 1, false, m);
}
void GPUProgram::setMatrix(const String &name, const Matrix3x3 &m)
{
    use();
    glUniformMatrix3fv(glGetUniformLocation(_program, name.c_str()), 1, false, m);
}
void GPUProgram::setMatrix(const String &name, const Matrix4x4 &m)
{
    use();
    glUniformMatrix4fv(glGetUniformLocation(_program, name.c_str()), 1, false, m);
}

void GPUProgram::setTexture(const String &name,
                            const Texture *texture, uint8 texUnit)
{
    // no texture
    if(!texture)
    {
        return;
    }

    use();
    texture->bind(texUnit);
//    if(texture->getType() != TEX_CUBE)    // TODO WTF?????
//    {
    glUniform1i(glGetUniformLocation(_program, name.c_str()), texUnit);
//    }
}

uint GPUProgram::_active = 0;

void GPUProgram::use() const
{
    // no shaders support, program not compiled
    if(!GLEE_VERSION_2_0 || !_compiled)
    {
        return;
    }

    // program already active
    if(_program == _active)
    {
        return;
    }

    glUseProgram(_program);
    _active = _program;
}

void GPUProgram::useFixedPipeline()
{
    // no opengl2 support
    if(!GLEE_VERSION_2_0)
    {
        return;
    }

    // fixed pipeline already active
    if(!_active)
    {
        return;
    }

    glUseProgram(0);
    _active = 0;
}

}
}

